using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LambdaGp.Core
{
    public class OperatorNode
    {
        private Operator @operator;

        public OperatorNode()
        {
            Children = new List<OperatorNode>();
        }

        public Operator Operator
        {
            get
            {
                return @operator;
            }

            set
            {
                @operator = value.ReplaceIfRequired();
            }
        }

        public List<OperatorNode> Children { get; private set; }

        public int Size
        {
            get
            {
                return 1 + Children.Sum(node => node.Size);
            }
        }

        public int Depth
        {
            get
            {
                if (Children.Count > 0)
                {
                    return 1 + Children.Max(node => node.Depth);
                }

                return 1;
            }
        }

        public void ExecuteOnEveryNodeInTree(Action<OperatorNode> action)
        {
            action(this);
            Children.ForEach(node => node.ExecuteOnEveryNodeInTree(action));
        }

        public List<OperatorNode> OperatorTreeToList()
        {
            List<OperatorNode> list = new List<OperatorNode>();
            AddOperatorTreeToList(list);
            return list;
        }

        public void AddOperatorTreeToList(ICollection<OperatorNode> list)
        {
            list.Add(this);
            Children.ForEach(node => node.AddOperatorTreeToList(list));
        }

        public Func<double> CompileToFunc(Individual individual)
        {
            return Operator.CompileToFunc(individual, this);
        }

        public OperatorNode Clone()
        {
            OperatorNode clone = new OperatorNode();
            clone.CopyFrom(this);
            return clone;
        }

        public void OperatorParameterMutate()
        {
            IParameterMutatable mutatable = Operator as IParameterMutatable;
            if (mutatable != null)
            {
                Operator = mutatable.ParameterMutate();
            }
        }

        public void CopyFrom(OperatorNode other)
        {
            Operator = other.Operator;
            Children.Clear();
            Children.AddRange(
                other.Children.Select(node => node.Clone()));
        }

        public override string ToString()
        {
            if (Operator.Arity == 0)
            {
                return Operator.Name;
            }

            List<string> arguments =
                Children
                    .Select(node => node.ToString())
                    .ToList();

            string argumentString = string.Join(" ", arguments.ToArray());

            return string.Format("({0} {1})", Operator.AsToken, argumentString);
        }      
 
        internal int GetDepthPosition(OperatorNode nodeAtFatherToCross)
        {
            if (this == nodeAtFatherToCross)
            {
                return 1;
            }

            if (Children.Count > 0)
            {
                return 1 + Children.Max(node => node.GetDepthPosition(nodeAtFatherToCross));
            }

            return 0;
        }

        internal void AddPrettyString(StringBuilder stringBuilder, string indent)
        {
            if (Operator.Arity == 0)
            {
                stringBuilder.Append(" ");
                stringBuilder.Append(Operator.Name);
            }
            else
            {
                if (stringBuilder.Length > 0)
                {
                    stringBuilder.Append(Environment.NewLine);
                }

                stringBuilder.AppendFormat("{0}({1}", indent, Operator.Name);
                Children.ForEach(node => node.AddPrettyString(stringBuilder, indent + "  "));
                stringBuilder.Append(")");
            }
        }

        internal virtual void AddCompactString(StringBuilder stringBuilder)
        {
            Operator.AddCompactString(stringBuilder);
            Children.ForEach(node => node.AddCompactString(stringBuilder));
        }
    }
}
